Gentle introduction
Make sure you have the shiny package installed on your computer. You can use code below or the “Packages” tab in lower right panel in Rstudio.
Have a good understanding of what Shiny apps can do and how it might be useful for you
Understand the core structure of Shiny apps: UI, Server, Reactivity
Learn how to create and run an App locally as well as deploy on ARI’s shiny account
Feel confident enough to grab example code from Rshiny gallery 🤞
Of course there is cheatsheet to check out…
knitr::include_graphics("shiny.pdf")
For background information on the dataset, see dataset summary
Okay, before we get started, I think that it is useful to give a pep talk. For the more programming-phobic of you, the Shiny code will look foreign. You first reaction might be “uggg, I am a researcher, not a programmer and I do not want to be programmer” and “are you serious after all this time I spent learning R coding and now I have to learn another type of programming??”
But, we swear that it is not a bad at it looks. Once you get beyond the foreignness of the code, we guarantee that with a little guidance, you can easily get started making your own apps in minutes. It is just about getting the basic structure and then having the confidence to do “trial-and-error” with borrowed code. Just be forewarned, shiny Apps can become addicting!!!
I will be using AI a fair a bit today. I have found really useful for getting started with shiny apps and for the basics (mostly what we covering here), it can really help. As noted in other days, it is helpful to have a few terminology/concepts under your belt when working with AI for shiny app creation. I will try to demonstrate this through this session. I will just work with Copilot (and maybe Claude for comparison?). I have more info in the page on AI extra topic.
We will work through an example of building a simple app that displays a graph depending on the conditions selected.
I am going to work with the built-in mtcars dataset for the example. I want an user to select a the number of cylinders a car has and plot the relationship between horsepower(hp) and fuel efficiency (mpg).
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
In Copilot, I typed: “build me a shiny app using the mtcars dataset. The UI will have a side panel that will allow the user to select the cyl type and the main panel will have two tabs. The first tab will show a plot of the relationship between hp and mpg for the cyl selected by the user. The plot will also have the linear regression line and confidence band that was created using lm(). The second tab will have the linear model outputs extracted from the model using broom::tidy()”
The coding output are shown below:
It does run successfully and the UI looks like the following:
ONE vs MULTIPLE files
For your app setup, you can either select a single file called app.R [like we did in the example] or you can create two files: ui.R and server.R. What this is doing is just breaking up the one file into two, otherwise basically the same. The advantage of the latter is in larger apps and for reusing code. We will stick with one file method for rest of today.
Shiny app has a straightforward structure comprising four main components: 1) global heading; 2) ui; 3) server; and 4) shinyApp.
Visual model of the file…
Let’s go through each component…
This is what the user will be using to explore your data/results/visualizations.
First step is determining what kind of layout you want. Single panel, main panel with sidepanel, tabs. May want to check Shiny gallery or layout for ideas.
In our example, AI produced the following UI code using fluidPage() then breaking into sidebarLayout() and then again into sidebarPanel() and mainPanel(). Within the mainPanel() section, it added tabsetPanel() to get the tabs.
Once layout is figured out, now you add in the input objects. In our example that was a drop-down menu that allowed the user to pick cylinder size.
Check the cheatsheet for basic options for inputs or search Shiny gallery
Few key takeways
basically creating html using simple functions
functions are separated by commas!!!!
And so many nested brackets!!!!
In the UI, an input list is created from the UI design and it is used to send user inputs to the server.
Takeways
like an R list
setup by the UI
can only be changed by the user (immutable from being changed in the server code)
The server is the heart of the app. Let revisit the visual model…
Three main key bits…
You have the inputs coming in that will be used
R code that will take the inputs and convert to new output(s)
the output list is created and exported back to UI
Let’s look a little closer at the server code
Key takeways
not separated by commas
more like R programming but order does not matter between components!!!!!
Within renderXXX or reactive() R code and order does matter here. Note the double-brackets needed when multiple lines
Our the server created output$hp_mpg_plot using renderPlot and output$model_summary using renderTable and the ui outputs using plotOutput and tableOutput. You get these pairs of renderXXX({}) and xxxOutput().
Look at the cheatsheet for a partial list of pairings…
Note - this is not a comprehensive list and packages might have then own renderXxx() and xxxOutput()
Couple more examples…
The style of programming used in shiny apps is called “reactive programming”. The purpose of this programming style is to keep inputs and outputs in sync. To do this efficiency, you only want to update outputs that need to be changed. So the rule is “change if and only if inputs change”.
In our example we had one such case. This reactive function was then used in both renderXXX functions.
The two key components of this are called:
lazy - only do work when called to [“procrastinate as long as possible”]
cache - save the results produced when first called and use that until something changes
You will see a variety of functions that are doing “extra” bits in the programming:
We will not go into depth here but just want you to be aware and when you see them, know that they are doing extra stuff and a bit of efficiency stuff. For more complicated apps, they are essential to get a handle of.
As noted about, search the Shiny gallery to get ideas of additional options of what you can do. On this website, it provides all the ui/server code on a GitHub repository, so you can just grab the code.
Another good place to search for additional extensions for shiny is Shiny Awesome which has a curated list and some of the stuff, is well, awesome! (and overwhelming) The links often lead you to GitHub pages.
As Shiny apps are all about interactivity, you may want to use interactive plots. Check the following webpage for a examples of packages that have interactivity.
I will show a few common ones below:
mapview packageThe mapview package can be used to set up a simple, quick leaflet. This would be most useful when the leaflet is created on startup (not adding in reactivity like adding or deleting data being shown). For instance, you might want to show all the sites surveyed and attach metadata (e.g. dates surveyed, number of fish caught, number of plots) to that point.
Useful shiny functions for Server/UI…
mapview::renderMapview() # server side
mapview::mapviewOutput() # UI side
leaflet packageNow, if you are going to build maps that will change with user input, it is best to build from “scratch” using leaflet package (and probably leaflet.extra package + others)
When you have user inputs affecting the map shown, you want to try to avoid rebuilding the map object and rather just modify the elements that the user wants changed (e.g. drop lines and add points instead). This requires using reactive programming and observe() functions. Check out Nev’s ibis app that does this. Below just shows simple leaflet code:
Useful shiny functions for Server/UI…
leaflet::leafletOutput() # UI side
leaflet::renderLeaflet() # server side - creates the basemap
leaflet::leafletProxy() # this is the server function that updates the map
Now, plotly package does a great job at interactive plots. You can write you code in ggplot and then just convert as shown in code below.
library(ggplot2)
library(plotly)
f <- ggplot( cars, aes(speed, dist) ) + geom_point() + geom_smooth()
plotly::ggplotly(f)
Useful shiny functions for Server/UI…
plotly::renderPlotly()
plotly::plotlyOutput()
Again, you can build your plots using ggplot and convert. I like this one for linking up plots…below, put your cursor over a dot on the left or a bar on the right. If you know CSS, it can be pretty powerful…
library(ggiraph)
library(tidyverse)
library(patchwork)
mtcars_db <- rownames_to_column(mtcars, var = "carname")
# First plot: Scatter plot
fig_pt <- ggplot(
data = mtcars_db,
mapping = aes(
x = disp, y = qsec,
tooltip = carname, data_id = carname
)
) +
geom_point_interactive(
size = 3, hover_nearest = TRUE
) +
labs(
title = "Displacement vs Quarter Mile",
x = "Displacement", y = "Quarter Mile"
) +
theme_bw()
# Second plot: Bar plot
fig_bar <- ggplot(
data = mtcars_db,
mapping = aes(
x = reorder(carname, mpg), y = mpg,
tooltip = paste("Car:", carname, "<br>MPG:", mpg),
data_id = carname
)
) +
geom_col_interactive(fill = "skyblue") +
coord_flip() +
labs(
title = "Miles per Gallon by Car",
x = "Car", y = "Miles per Gallon"
) +
theme_bw()
# Combine the plots using patchwork
combined_plot <- fig_pt + fig_bar + plot_layout(ncol = 2)
# Combine the plots using cowplot
# combined_plot <- cowplot::plot_grid(fig_pt, fig_bar, ncol=2)
# Create a single interactive plot with both subplots
interactive_plot <- girafe(ggobj = combined_plot)
# Set options for the interactive plot
girafe_options(
interactive_plot,
opts_hover(css = "fill:cyan;stroke:black;cursor:pointer;"),
opts_selection(type = "single", css = "fill:red;stroke:black;")
)
Useful shiny functions for Server/UI…
ggiraph::renderGirafe()
ggiraph::ggiraphOutput()
You can download our version:
For useful resources: https://shiny.posit.co/r/articles/
Good for basic shiny https://mastering-shiny.org/
Engineering Production-Grade Shiny Apps
https://engineering-shiny.org/index.html
As your Apps improve in capabilities, you may want to have users upload inputs (e.g. spatial layer, data)
temporary results on the server for just the session
save to cloud: dropbox, Amazon (AWS), Gdrive
save to a cloud database
If your app produces output for the user, you may want to have a button so that they can download those results to their computer.
This package helps with setting up dashboard layouts (columns, rows, matrix) as well as a suite of html objects (e.g. messages, notifications), aesthetics (“skins”), and other more bespoke tools. Check out https://rstudio.github.io/shinydashboard/ for examples and overview.
Flexdashboard can be useful way to quickly create a layout using Rmarkdown-like file. At the simplest, you can create an interactive dashboard with no reactivity. If you want reactivity (e.g. inputs), then you can still use the template and integrate selectInput() functions and renderXXX() functions without explicitly defining ui/server. It is a bit of a hybrid approach between Rmarkdown/Quarto and shiny App. Not sure how much this package is still being developed??
For a range of examples: https://rstudio.github.io/flexdashboard/articles/examples.html
An example as a shiny App run in a Rmarkdown file, see https://jjallaire.shinyapps.io/shiny-ggplot2-diamonds/.
Ibis Tracker - Nev’s masterpiece
https://arisci.shinyapps.io/ibisTracker/